Micron Document
🎖️GitЯра🎖️

Node / meshtastic / Meshtastic-Android / files / app / src / main / java / com / geeksville / mesh / repository / radio / BluetoothInterface.kt

Displaying Raw • Download

app/src/main/java/com/geeksville/mesh/repository/radio/BluetoothInterface.kt ui_message_list (be3f882f) Text, 20.01 KB

T8b949e/*
* Copyright (c) 2025 Meshtastic LLC
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/

Tff7b72package T7ee787com.geeksville.mesh.repository.radio

Tff7b72import T7ee787android.annotation.SuppressLint
Tff7b72import T7ee787android.app.Application
Tff7b72import T7ee787android.bluetooth.BluetoothGattCharacteristic
Tff7b72import T7ee787android.bluetooth.BluetoothGattService
Tff7b72import T7ee787com.geeksville.mesh.android.Logging
Tff7b72import T7ee787com.geeksville.mesh.concurrent.handledLaunch
Tff7b72import T7ee787com.geeksville.mesh.repository.bluetooth.BluetoothRepository
Tff7b72import T7ee787com.geeksville.mesh.service.BLECharacteristicNotFoundException
Tff7b72import T7ee787com.geeksville.mesh.service.BLEConnectionClosing
Tff7b72import T7ee787com.geeksville.mesh.service.BLEException
Tff7b72import T7ee787com.geeksville.mesh.service.RadioNotConnectedException
Tff7b72import T7ee787com.geeksville.mesh.service.SafeBluetooth
Tff7b72import T7ee787com.geeksville.mesh.util.anonymize
Tff7b72import T7ee787com.geeksville.mesh.util.exceptionReporter
Tff7b72import T7ee787com.geeksville.mesh.util.ignoreException
Tff7b72import T7ee787dagger.assisted.Assisted
Tff7b72import T7ee787dagger.assisted.AssistedInject
Tff7b72import T7ee787kotlinx.coroutines.CancellationException
Tff7b72import T7ee787kotlinx.coroutines.Job
Tff7b72import T7ee787kotlinx.coroutines.delay
Tff7b72import T7ee787java.lang.reflect.Method
Tff7b72import T7ee787java.util.UUID

T8b949e/* Info for the esp32 device side code. See that source for the 'gold' standard docs on this interface.

MeshBluetoothService UUID 6ba1b218-15a8-461f-9fa8-5dcae273eafd

FIXME - notify vs indication for fromradio output. Using notify for now, not sure if that is best
FIXME - in the esp32 mesh management code, occasionally mirror the current net db to flash, so that if we reboot we still have a good guess of users who are out there.
FIXME - make sure this protocol is guaranteed robust and won't drop packets

"According to the BLE specification the notification length can be max ATT_MTU - 3. The 3 bytes subtracted is the 3-byte header(OP-code (operation, 1 byte) and the attribute handle (2 bytes)).
In BLE 4.1 the ATT_MTU is 23 bytes (20 bytes for payload), but in BLE 4.2 the ATT_MTU can be negotiated up to 247 bytes."

MAXPACKET is 256? look into what the lora lib uses. FIXME

Characteristics:
UUID
properties
description

8ba2bcc2-ee02-4a55-a531-c525c5e454d5
read
fromradio - contains a newly received packet destined towards the phone (up to MAXPACKET bytes? per packet).
After reading the esp32 will put the next packet in this mailbox. If the FIFO is empty it will put an empty packet in this
mailbox.

f75c76d2-129e-4dad-a1dd-7866124401e7
write
toradio - write ToRadio protobufs to this charstic to send them (up to MAXPACKET len)

ed9da18c-a800-4f66-a670-aa7547e34453
read|notify|write
fromnum - the current packet # in the message waiting inside fromradio, if the phone sees this notify it should read messages
until it catches up with this number.
The phone can write to this register to go backwards up to FIXME packets, to handle the rare case of a fromradio packet was dropped after the esp32
callback was called, but before it arrives at the phone. If the phone writes to this register the esp32 will discard older packets and put the next packet >= fromnum in fromradio.
When the esp32 advances fromnum, it will delay doing the notify by 100ms, in the hopes that the notify will never actally need to be sent if the phone is already pulling from fromradio.
Note: that if the phone ever sees this number decrease, it means the esp32 has rebooted.

Re: queue management
Not all messages are kept in the fromradio queue (filtered based on SubPacket):
* only the most recent Position and User messages for a particular node are kept
* all Data SubPackets are kept
* No WantNodeNum / DenyNodeNum messages are kept
A variable keepAllPackets, if set to true will suppress this behavior and instead keep everything for forwarding to the phone (for debugging)

*/

T8b949e/**
* Handles the bluetooth link with a mesh radio device. Does not cache any device state, just does bluetooth comms
* etc...
*
* This service is not exposed outside of this process.
*
* Note - this class intentionally dumb. It doesn't understand protobuf framing etc... It is designed to be simple so it
* can be stubbed out with a simulated version as needed.
*/
Tf0883e@SuppressLintTb4b4b4(Ta5d6ff"Ta5d6ffMissingPermissionTa5d6ff"Tb4b4b4)
Tff7b72class T56d364BluetoothInterface
Tf0883e@AssistedInject
Tff7b72constructorTb4b4b4(
Te6edf3contextTb4b4b4: Te6edf3ApplicationTb4b4b4,
Te6edf3bluetoothRepositoryTb4b4b4: Te6edf3BluetoothRepositoryTb4b4b4,
Tff7b72private Tff7b72val Te6edf3serviceTb4b4b4: Te6edf3RadioInterfaceServiceTb4b4b4,
Tf0883e@Assisted Tff7b72val Te6edf3addressTb4b4b4: Tffa657StringTb4b4b4,
Tb4b4b4) Tb4b4b4: Te6edf3IRadioInterfaceTb4b4b4,
Te6edf3Logging Tb4b4b4{

Tff7b72companion Tff7b72object Tb4b4b4{
T8b949e// this service UUID is publicly visible for scanning
Tff7b72val Te6edf3BTM_SERVICE_UUIDTb4b4b4: Te6edf3UUID Tff7b72= Te6edf3UUIDTb4b4b4.Te6edf3fromStringTb4b4b4(Ta5d6ff"Ta5d6ff6ba1b218-15a8-461f-9fa8-5dcae273eafdTa5d6ff"Tb4b4b4)

Tff7b72val Te6edf3BTM_FROMRADIO_CHARACTERTb4b4b4: Te6edf3UUID Tff7b72= Te6edf3UUIDTb4b4b4.Te6edf3fromStringTb4b4b4(Ta5d6ff"Ta5d6ff2c55e69e-4993-11ed-b878-0242ac120002Ta5d6ff"Tb4b4b4)
Tff7b72val Te6edf3BTM_TORADIO_CHARACTERTb4b4b4: Te6edf3UUID Tff7b72= Te6edf3UUIDTb4b4b4.Te6edf3fromStringTb4b4b4(Ta5d6ff"Ta5d6fff75c76d2-129e-4dad-a1dd-7866124401e7Ta5d6ff"Tb4b4b4)
Tff7b72val Te6edf3BTM_FROMNUM_CHARACTERTb4b4b4: Te6edf3UUID Tff7b72= Te6edf3UUIDTb4b4b4.Te6edf3fromStringTb4b4b4(Ta5d6ff"Ta5d6ffed9da18c-a800-4f66-a670-aa7547e34453Ta5d6ff"Tb4b4b4)

T8b949e/**
* this is created in onCreate() We do an ugly hack of keeping it in the singleton so we can share it for the
* rare software update case
*/
Tf0883e@Volatile Tff7b72var Te6edf3safeTb4b4b4: Te6edf3SafeBluetooth? Tff7b72= Tff7b72null
Tb4b4b4}

T8b949e// Our BLE device
Tff7b72val Te6edf3device
Tff7b72getTb4b4b4(Tb4b4b4) Tff7b72=
Tb4b4b4(Te6edf3safe Tff7b72?: Tff7b72throw Te6edf3RadioNotConnectedExceptionTb4b4b4(Ta5d6ff"Ta5d6ffNo SafeBluetoothTa5d6ff"Tb4b4b4)Tb4b4b4)Tb4b4b4.Te6edf3gatt
Tff7b72?: Tff7b72throw Te6edf3RadioNotConnectedExceptionTb4b4b4(Ta5d6ff"Ta5d6ffNo GATTTa5d6ff"Tb4b4b4)

T8b949e// Our service - note - it is possible to get back a null response for getService if the device services haven't
T8b949e// yet been found
Tff7b72private Tff7b72val Te6edf3bservice
Tff7b72getTb4b4b4(Tb4b4b4)Tb4b4b4: Te6edf3BluetoothGattService Tff7b72=
Te6edf3deviceTb4b4b4.Te6edf3getServiceTb4b4b4(Te6edf3BTM_SERVICE_UUIDTb4b4b4) Tff7b72?: Tff7b72throw Te6edf3RadioNotConnectedExceptionTb4b4b4(Ta5d6ff"Ta5d6ffBLE service not foundTa5d6ff"Tb4b4b4)

Tf0883e@Volatile Tff7b72private Tff7b72var Te6edf3reconnectAttempts Tff7b72= T79c0ff0

Tff7b72private Tff7b72lateinit Tff7b72var Te6edf3fromNumTb4b4b4: Te6edf3BluetoothGattCharacteristic

T8b949e/**
* With the new rev2 api, our first send is to start the configure readbacks. In that case, rather than waiting for
* FromNum notifies - we try to just aggressively read all of the responses.
*/
Tff7b72private Tff7b72var Te6edf3isFirstSend Tff7b72= Tff7b72true

T8b949e// NRF52 targets do not need the nasty force refresh hack that ESP32 needs (because they keep their
T8b949e// BLE handles stable. So turn the hack off for these devices. FIXME - find a better way to know that the board is
T8b949e// NRF52 based
T8b949e// and Amazon fire devices seem to not need this hack either
T8b949e// Build.MANUFACTURER != "Amazon" &&
Tff7b72private Tff7b72var Te6edf3needForceRefresh Tff7b72= Tff7b72!Te6edf3addressTb4b4b4.Te6edf3startsWithTb4b4b4(Ta5d6ff"Ta5d6ffFD:10:04Ta5d6ff"Tb4b4b4)

Tff7b72init Tb4b4b4{
T8b949e// Note: this call does no comms, it just creates the device object (even if the
T8b949e// device is off/not connected)
Tff7b72val Te6edf3device Tff7b72= Te6edf3bluetoothRepositoryTb4b4b4.Te6edf3getRemoteDeviceTb4b4b4(Te6edf3addressTb4b4b4)
Tff7b72if Tb4b4b4(Te6edf3device Tff7b72!Tff7b72= Tff7b72nullTb4b4b4) Tb4b4b4{
Te6edf3infoTb4b4b4(Ta5d6ff"Ta5d6ffCreating radio interface service. device=Tffd700${Te6edf3addressTb4b4b4.Te6edf3anonymizeTffd700}Ta5d6ff"Tb4b4b4)

T8b949e// Note this constructor also does no comm
Tff7b72val Te6edf3s Tff7b72= Te6edf3SafeBluetoothTb4b4b4(Te6edf3contextTb4b4b4, Te6edf3deviceTb4b4b4)
Te6edf3safe Tff7b72= Te6edf3s

Te6edf3startConnectTb4b4b4(Tb4b4b4)
Tb4b4b4} Tff7b72else Tb4b4b4{
Te6edf3errormsgTb4b4b4(Ta5d6ff"Ta5d6ffBluetooth adapter not found, assuming running on the emulator!Ta5d6ff"Tb4b4b4)
Tb4b4b4}
Tb4b4b4}

T8b949e// / Send a packet/command out the radio link
Tff7b72override Tff7b72fun Td2a8ffhandleSendToRadioTb4b4b4(Te6edf3pTb4b4b4: Te6edf3ByteArrayTb4b4b4) Tb4b4b4{
Tff7b72try Tb4b4b4{
Te6edf3safeTff7b72?.Te6edf3let Tb4b4b4{ Te6edf3s Tff7b72-Tff7b72>
Tff7b72val Te6edf3uuid Tff7b72= Te6edf3BTM_TORADIO_CHARACTER
Te6edf3debugTb4b4b4(Ta5d6ff"Ta5d6ffqueuing Tffd700${Te6edf3pTb4b4b4.Te6edf3sizeTffd700}Ta5d6ff bytes to Tffd700$Te6edf3uuidTa5d6ff"Tb4b4b4)

T8b949e// Note: we generate a new characteristic each time, because we are about to
T8b949e// change the data and we want the data stored in the closure
Tff7b72val Te6edf3toRadio Tff7b72= Te6edf3getCharacteristicTb4b4b4(Te6edf3uuidTb4b4b4)

Te6edf3sTb4b4b4.Te6edf3asyncWriteCharacteristicTb4b4b4(Te6edf3toRadioTb4b4b4, Te6edf3pTb4b4b4) Tb4b4b4{ Te6edf3r Tff7b72-Tff7b72>
Tff7b72try Tb4b4b4{
Te6edf3rTb4b4b4.Te6edf3getOrThrowTb4b4b4(Tb4b4b4)
Te6edf3debugTb4b4b4(Ta5d6ff"Ta5d6ffwrite of Tffd700${Te6edf3pTb4b4b4.Te6edf3sizeTffd700}Ta5d6ff bytes to Tffd700$Te6edf3uuidTa5d6ff completedTa5d6ff"Tb4b4b4)

Tff7b72if Tb4b4b4(Te6edf3isFirstSendTb4b4b4) Tb4b4b4{
Te6edf3isFirstSend Tff7b72= Tff7b72false
Te6edf3doReadFromRadioTb4b4b4(Tff7b72falseTb4b4b4)
Tb4b4b4}
Tb4b4b4} Tff7b72catch Tb4b4b4(Te6edf3exTb4b4b4: Te6edf3ExceptionTb4b4b4) Tb4b4b4{
Te6edf3scheduleReconnectTb4b4b4(Ta5d6ff"Ta5d6fferror during asyncWriteCharacteristic - disconnecting, Tffd700${Te6edf3exTb4b4b4.Te6edf3messageTffd700}Ta5d6ff"Tb4b4b4)
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
Tb4b4b4} Tff7b72catch Tb4b4b4(Te6edf3exTb4b4b4: Te6edf3BLEExceptionTb4b4b4) Tb4b4b4{
Te6edf3scheduleReconnectTb4b4b4(Ta5d6ff"Ta5d6fferror during handleSendToRadio Tffd700${Te6edf3exTb4b4b4.Te6edf3messageTffd700}Ta5d6ff"Tb4b4b4)
Tb4b4b4}
Tb4b4b4}

Tf0883e@Volatile Tff7b72private Tff7b72var Te6edf3reconnectJobTb4b4b4: Te6edf3Job? Tff7b72= Tff7b72null

T8b949e/** We had some problem, schedule a reconnection attempt (if one isn't already queued) */
Tff7b72private Tff7b72fun Td2a8ffscheduleReconnectTb4b4b4(Te6edf3reasonTb4b4b4: Tffa657StringTb4b4b4) Tb4b4b4{
Tff7b72if Tb4b4b4(Te6edf3reconnectJob Tff7b72=Tff7b72= Tff7b72nullTb4b4b4) Tb4b4b4{
Te6edf3warnTb4b4b4(Ta5d6ff"Ta5d6ffScheduling reconnect because Tffd700$Te6edf3reasonTa5d6ff"Tb4b4b4)
Te6edf3reconnectJob Tff7b72= Te6edf3serviceTb4b4b4.Te6edf3serviceScopeTb4b4b4.Te6edf3handledLaunch Tb4b4b4{ Te6edf3retryDueToExceptionTb4b4b4(Tb4b4b4) Tb4b4b4}
Tb4b4b4} Tff7b72else Tb4b4b4{
Te6edf3warnTb4b4b4(Ta5d6ff"Ta5d6ffSkipping reconnect for Tffd700$Te6edf3reasonTa5d6ff"Tb4b4b4)
Tb4b4b4}
Tb4b4b4}

T8b949e// / Attempt to read from the fromRadio mailbox, if data is found broadcast it to android apps
Tff7b72private Tff7b72fun Td2a8ffdoReadFromRadioTb4b4b4(Te6edf3firstReadTb4b4b4: Tffa657BooleanTb4b4b4) Tb4b4b4{
Te6edf3safeTff7b72?.Te6edf3let Tb4b4b4{ Te6edf3s Tff7b72-Tff7b72>
Tff7b72val Te6edf3fromRadio Tff7b72= Te6edf3getCharacteristicTb4b4b4(Te6edf3BTM_FROMRADIO_CHARACTERTb4b4b4)
Te6edf3sTb4b4b4.Te6edf3asyncReadCharacteristicTb4b4b4(Te6edf3fromRadioTb4b4b4) Tb4b4b4{
Tff7b72try Tb4b4b4{
Tff7b72val Te6edf3b Tff7b72=
Tffa657itTb4b4b4.Te6edf3getOrThrowTb4b4b4(Tb4b4b4)
Tb4b4b4.Te6edf3value
Tb4b4b4.Te6edf3cloneTb4b4b4(Tb4b4b4) T8b949e// We clone the array just in case, I'm not sure if they keep reusing the array

Tff7b72if Tb4b4b4(Te6edf3bTb4b4b4.Te6edf3isNotEmptyTb4b4b4(Tb4b4b4)Tb4b4b4) Tb4b4b4{
Te6edf3debugTb4b4b4(Ta5d6ff"Ta5d6ffReceived Tffd700${Te6edf3bTb4b4b4.Te6edf3sizeTffd700}Ta5d6ff bytes from radioTa5d6ff"Tb4b4b4)
Te6edf3serviceTb4b4b4.Te6edf3handleFromRadioTb4b4b4(Te6edf3bTb4b4b4)

T8b949e// Queue up another read, until we run out of packets
Te6edf3doReadFromRadioTb4b4b4(Te6edf3firstReadTb4b4b4)
Tb4b4b4} Tff7b72else Tb4b4b4{
Te6edf3debugTb4b4b4(Ta5d6ff"Ta5d6ffDone reading from radio, fromradio is emptyTa5d6ff"Tb4b4b4)
Tff7b72if Tb4b4b4(Te6edf3firstReadTb4b4b4) Tb4b4b4{
T8b949e// If we just finished our initial download, now we want to start listening for notifies
Te6edf3startWatchingFromNumTb4b4b4(Tb4b4b4)
Tb4b4b4}
Tb4b4b4}
Tb4b4b4} Tff7b72catch Tb4b4b4(Te6edf3exTb4b4b4: Te6edf3BLEExceptionTb4b4b4) Tb4b4b4{
Te6edf3scheduleReconnectTb4b4b4(Ta5d6ff"Ta5d6fferror during doReadFromRadio - disconnecting, Tffd700${Te6edf3exTb4b4b4.Te6edf3messageTffd700}Ta5d6ff"Tb4b4b4)
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}

T8b949e/**
* Android caches old services. But our service is still changing often, so force it to reread the service
* definitions every time
*/
Tff7b72private Tff7b72fun Td2a8ffforceServiceRefreshTb4b4b4(Tb4b4b4) Tb4b4b4{
Te6edf3exceptionReporter Tb4b4b4{
T8b949e// If the gatt has been destroyed, skip the refresh attempt
Te6edf3safeTff7b72?.Te6edf3gattTff7b72?.Te6edf3let Tb4b4b4{ Te6edf3gatt Tff7b72-Tff7b72>
Te6edf3debugTb4b4b4(Ta5d6ff"Ta5d6ffDOING FORCE REFRESHTa5d6ff"Tb4b4b4)
Tff7b72val Te6edf3refreshTb4b4b4: Te6edf3Method Tff7b72= Te6edf3gattTb4b4b4.Te6edf3javaClassTb4b4b4.Te6edf3getMethodTb4b4b4(Ta5d6ff"Ta5d6ffrefreshTa5d6ff"Tb4b4b4)
Te6edf3refreshTb4b4b4.Te6edf3invokeTb4b4b4(Te6edf3gattTb4b4b4)
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}

T8b949e// / We only force service refresh the _first_ time we connect to the device. Thereafter it is assumed the firmware
T8b949e// didn't change
Tf0883e@SuppressTb4b4b4(Ta5d6ff"Ta5d6ffUnusedPrivateMemberTa5d6ff"Tb4b4b4)
Tff7b72private Tff7b72var Te6edf3hasForcedRefresh Tff7b72= Tff7b72false

Tf0883e@Volatile Tff7b72var Te6edf3fromNumChanged Tff7b72= Tff7b72false

Tff7b72private Tff7b72fun Td2a8ffstartWatchingFromNumTb4b4b4(Tb4b4b4) Tb4b4b4{
Te6edf3safeTff7b72?.Te6edf3setNotifyTb4b4b4(Te6edf3fromNumTb4b4b4, Tff7b72trueTb4b4b4) Tb4b4b4{
T8b949e// We might get multiple notifies before we get around to reading from the radio - so just set one flag
Te6edf3fromNumChanged Tff7b72= Tff7b72true
Te6edf3serviceTb4b4b4.Te6edf3serviceScopeTb4b4b4.Te6edf3handledLaunch Tb4b4b4{
Tff7b72try Tb4b4b4{
Tff7b72if Tb4b4b4(Te6edf3fromNumChangedTb4b4b4) Tb4b4b4{
Te6edf3fromNumChanged Tff7b72= Tff7b72false
Te6edf3debugTb4b4b4(Ta5d6ff"Ta5d6fffromNum changed, so we are reading new messagesTa5d6ff"Tb4b4b4)
Te6edf3doReadFromRadioTb4b4b4(Tff7b72falseTb4b4b4)
Tb4b4b4}
Tb4b4b4} Tff7b72catch Tb4b4b4(Te6edf3eTb4b4b4: Te6edf3RadioNotConnectedExceptionTb4b4b4) Tb4b4b4{
T8b949e// Don't report autobugs for this, getting an exception here is expected behavior
Te6edf3errormsgTb4b4b4(Ta5d6ff"Ta5d6ffEnding FromNum read, radio not connectedTa5d6ff"Tb4b4b4, Te6edf3eTb4b4b4)
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}

Tff7b72private Tff7b72val Te6edf3maxReconnectionAttempts Tff7b72= T79c0ff6

T8b949e/**
* Some buggy BLE stacks can fail on initial connect, with either missing services or missing characteristics. If
* that happens we disconnect and try again when the device reenumerates.
*/
Tff7b72private Tff7b72suspend Tff7b72fun Td2a8ffretryDueToExceptionTb4b4b4(Tb4b4b4) Tff7b72= Tff7b72try Tb4b4b4{
T8b949e// / We gracefully handle safe being null because this can occur if someone has unpaired from our device -
T8b949e// just abandon the reconnect attempt
Tff7b72val Te6edf3s Tff7b72= Te6edf3safe
Tff7b72if Tb4b4b4(Te6edf3s Tff7b72!Tff7b72= Tff7b72nullTb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3backoffMillis Tff7b72= Tb4b4b4(T79c0ff1T79c0ff0T79c0ff0T79c0ff0 Tff7b72* Tb4b4b4(T79c0ff1 Te6edf3shl Te6edf3reconnectAttemptsTb4b4b4.Te6edf3coerceAtMostTb4b4b4(Te6edf3maxReconnectionAttemptsTb4b4b4)Tb4b4b4)Tb4b4b4)Tb4b4b4.Te6edf3toLongTb4b4b4(Tb4b4b4)
T8b949e// Exponential backoff, capped at 64s
Te6edf3reconnectAttemptsTff7b72+Tff7b72+
Te6edf3warnTb4b4b4(
Ta5d6ff"Ta5d6ffForcing disconnect and hopefully device will comebackTa5d6ff" Tff7b72+
Ta5d6ff"Ta5d6ff (disabling forced refresh). Reconnect attempt Tffd700$Te6edf3reconnectAttemptsTa5d6ff,Ta5d6ff" Tff7b72+
Ta5d6ff"Ta5d6ff waiting Tffd700${Te6edf3backoffMillisTffd700}Ta5d6ffms.Ta5d6ff"Tb4b4b4,
Tb4b4b4)

T8b949e// The following optimization is not currently correct - because the device might be sleeping and come
T8b949e// back with different BLE handles
T8b949e// hasForcedRefresh = true // We've already tossed any old service caches, no need to do it again

T8b949e// Make sure the old connection was killed
Te6edf3ignoreException Tb4b4b4{ Te6edf3sTb4b4b4.Te6edf3closeConnectionTb4b4b4(Tb4b4b4) Tb4b4b4}

Te6edf3serviceTb4b4b4.Te6edf3onDisconnectTb4b4b4(Tff7b72falseTb4b4b4) T8b949e// assume we will fail
Te6edf3delayTb4b4b4(Te6edf3backoffMillisTb4b4b4) T8b949e// Give some nasty time for buggy BLE stacks to shutdown
Te6edf3reconnectJob Tff7b72= Tff7b72null T8b949e// Any new reconnect requests after this will be allowed to run
Te6edf3warnTb4b4b4(Ta5d6ff"Ta5d6ffAttempting reconnectTa5d6ff"Tb4b4b4)
Tff7b72if Tb4b4b4(Te6edf3safe Tff7b72!Tff7b72= Tff7b72nullTb4b4b4) Tb4b4b4{
T8b949e// check again, because we just slept, and someone might have closed our interface
Te6edf3startConnectTb4b4b4(Tb4b4b4)
Tb4b4b4} Tff7b72else Tb4b4b4{
Te6edf3warnTb4b4b4(Ta5d6ff"Ta5d6ffNot connecting, because safe==null, someone must have closed usTa5d6ff"Tb4b4b4)
Tb4b4b4}
Tb4b4b4} Tff7b72else Tb4b4b4{
Te6edf3warnTb4b4b4(Ta5d6ff"Ta5d6ffAbandoning reconnect because safe==null, someone must have closed the deviceTa5d6ff"Tb4b4b4)
Tb4b4b4}
Tb4b4b4} Tff7b72catch Tb4b4b4(Te6edf3exTb4b4b4: Te6edf3CancellationExceptionTb4b4b4) Tb4b4b4{
Te6edf3warnTb4b4b4(Ta5d6ff"Ta5d6ffretryDueToException was cancelledTa5d6ff"Tb4b4b4)
Tb4b4b4} Tff7b72finally Tb4b4b4{
Te6edf3reconnectJob Tff7b72= Tff7b72null
Tb4b4b4}

T8b949e// / We only try to set MTU once, because some buggy implementations fail
Tf0883e@Volatile Tff7b72private Tff7b72var Te6edf3shouldSetMtu Tff7b72= Tff7b72true

T8b949e// / For testing
Tf0883e@SuppressTb4b4b4(Ta5d6ff"Ta5d6ffUnusedPrivateMemberTa5d6ff"Tb4b4b4)
Tf0883e@Volatile
Tff7b72private Tff7b72var Te6edf3isFirstTime Tff7b72= Tff7b72true

Tff7b72private Tff7b72fun Td2a8ffdoDiscoverServicesAndInitTb4b4b4(Tb4b4b4) Tb4b4b4{
Tff7b72val Te6edf3s Tff7b72= Te6edf3safe
Tff7b72if Tb4b4b4(Te6edf3s Tff7b72=Tff7b72= Tff7b72nullTb4b4b4) Tb4b4b4{
Te6edf3warnTb4b4b4(Ta5d6ff"Ta5d6ffInterface is shutting down, so skipping discoverTa5d6ff"Tb4b4b4)
Tb4b4b4} Tff7b72else Tb4b4b4{
Te6edf3sTb4b4b4.Te6edf3asyncDiscoverServices Tb4b4b4{ Te6edf3discRes Tff7b72-Tff7b72>
Tff7b72try Tb4b4b4{
Te6edf3discResTb4b4b4.Te6edf3getOrThrowTb4b4b4(Tb4b4b4)

Te6edf3serviceTb4b4b4.Te6edf3serviceScopeTb4b4b4.Te6edf3handledLaunch Tb4b4b4{
Tff7b72try Tb4b4b4{
Te6edf3debugTb4b4b4(Ta5d6ff"Ta5d6ffDiscovered services!Ta5d6ff"Tb4b4b4)
Te6edf3delayTb4b4b4(
T79c0ff1T79c0ff0T79c0ff0T79c0ff0Tb4b4b4,
Tb4b4b4) T8b949e// android BLE is buggy and needs a 1000ms sleep before calling getChracteristic, or you
T8b949e// might get back null

T8b949e/* if (isFirstTime) {
isFirstTime = false
throw BLEException("Faking a BLE failure")
} */

Te6edf3fromNum Tff7b72= Te6edf3getCharacteristicTb4b4b4(Te6edf3BTM_FROMNUM_CHARACTERTb4b4b4)

T8b949e// We treat the first send by a client as special
Te6edf3isFirstSend Tff7b72= Tff7b72true

T8b949e// Now tell clients they can (finally use the api)
Te6edf3serviceTb4b4b4.Te6edf3onConnectTb4b4b4(Tb4b4b4)

T8b949e// Immediately broadcast any queued packets sitting on the device
Te6edf3doReadFromRadioTb4b4b4(Tff7b72trueTb4b4b4)
Tb4b4b4} Tff7b72catch Tb4b4b4(Te6edf3exTb4b4b4: Te6edf3BLEExceptionTb4b4b4) Tb4b4b4{
Te6edf3scheduleReconnectTb4b4b4(Ta5d6ff"Ta5d6ffUnexpected error in initial device enumeration, forcing disconnect Tffd700$Te6edf3exTa5d6ff"Tb4b4b4)
Tb4b4b4}
Tb4b4b4}
Tb4b4b4} Tff7b72catch Tb4b4b4(Te6edf3exTb4b4b4: Te6edf3BLEExceptionTb4b4b4) Tb4b4b4{
Tff7b72if Tb4b4b4(Te6edf3sTb4b4b4.Te6edf3gatt Tff7b72=Tff7b72= Tff7b72nullTb4b4b4) Tb4b4b4{
Te6edf3warnTb4b4b4(Ta5d6ff"Ta5d6ffGATT was closed while discovering, assume we are shutting downTa5d6ff"Tb4b4b4)
Tb4b4b4} Tff7b72else Tb4b4b4{
Te6edf3scheduleReconnectTb4b4b4(Ta5d6ff"Ta5d6ffUnexpected error discovering services, forcing disconnect Tffd700$Te6edf3exTa5d6ff"Tb4b4b4)
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}

Tff7b72private Tff7b72fun Td2a8ffonConnectTb4b4b4(Te6edf3connResTb4b4b4: Te6edf3ResultTff7b72<Tffa657UnitTff7b72>Tb4b4b4) Tb4b4b4{
T8b949e// This callback is invoked after we are connected

Te6edf3connResTb4b4b4.Te6edf3getOrThrowTb4b4b4(Tb4b4b4)

Te6edf3reconnectAttempts Tff7b72= T79c0ff0 T8b949e// Reset backoff on successful connection

Te6edf3serviceTb4b4b4.Te6edf3serviceScopeTb4b4b4.Te6edf3handledLaunch Tb4b4b4{
Te6edf3infoTb4b4b4(Ta5d6ff"Ta5d6ffConnected to radio!Ta5d6ff"Tb4b4b4)

Tff7b72if Tb4b4b4(
Te6edf3needForceRefresh
Tb4b4b4) Tb4b4b4{ T8b949e// Our ESP32 code doesn't properly generate "service changed" indications. Therefore we need to force a
T8b949e// refresh on initial start
T8b949e// needForceRefresh = false // In fact, because of tearing down BLE in sleep on the ESP32, our handle #
T8b949e// assignments are not stable across sleep - so we much refetch every time
Te6edf3forceServiceRefreshTb4b4b4(Tb4b4b4) T8b949e// this article says android should not be caching, but it does on some phones:
T8b949e// https://punchthrough.com/attribute-caching-in-ble-advantages-and-pitfalls/

Te6edf3delayTb4b4b4(
T79c0ff5T79c0ff0T79c0ff0Tb4b4b4,
Tb4b4b4) T8b949e// From looking at the android C code it seems that we need to give some time for the refresh message
T8b949e// to reach that worked _before_ we try to set mtu/get services
T8b949e// 200ms was not enough on an Amazon Fire
Tb4b4b4}

T8b949e// we begin by setting our MTU size as high as it can go (if we can)
Tff7b72if Tb4b4b4(Te6edf3shouldSetMtuTb4b4b4) Tb4b4b4{
Te6edf3safeTff7b72?.Te6edf3asyncRequestMtuTb4b4b4(T79c0ff5T79c0ff1T79c0ff2Tb4b4b4) Tb4b4b4{ Te6edf3mtuRes Tff7b72-Tff7b72>
Tff7b72try Tb4b4b4{
Te6edf3mtuResTb4b4b4.Te6edf3getOrThrowTb4b4b4(Tb4b4b4)
Te6edf3debugTb4b4b4(Ta5d6ff"Ta5d6ffMTU change attemptedTa5d6ff"Tb4b4b4)

T8b949e// throw BLEException("Test MTU set failed")

Te6edf3doDiscoverServicesAndInitTb4b4b4(Tb4b4b4)
Tb4b4b4} Tff7b72catch Tb4b4b4(Te6edf3exTb4b4b4: Te6edf3BLEExceptionTb4b4b4) Tb4b4b4{
Te6edf3shouldSetMtu Tff7b72= Tff7b72false
Te6edf3scheduleReconnectTb4b4b4(Ta5d6ff"Ta5d6ffGiving up on setting MTUs, forcing disconnect Tffd700$Te6edf3exTa5d6ff"Tb4b4b4)
Tb4b4b4}
Tb4b4b4}
Tb4b4b4} Tff7b72else Tb4b4b4{
Te6edf3doDiscoverServicesAndInitTb4b4b4(Tb4b4b4)
Tb4b4b4}
Tb4b4b4}
Tb4b4b4}

Tff7b72override Tff7b72fun Td2a8ffcloseTb4b4b4(Tb4b4b4) Tb4b4b4{
Te6edf3reconnectJobTff7b72?.Te6edf3cancelTb4b4b4(Tb4b4b4) T8b949e// Cancel any queued reconnect attempts

Tff7b72if Tb4b4b4(Te6edf3safe Tff7b72!Tff7b72= Tff7b72nullTb4b4b4) Tb4b4b4{
Te6edf3infoTb4b4b4(Ta5d6ff"Ta5d6ffClosing BluetoothInterfaceTa5d6ff"Tb4b4b4)
Tff7b72val Te6edf3s Tff7b72= Te6edf3safe
Te6edf3safe Tff7b72= Tff7b72null T8b949e// We do this first, because if we throw we still want to mark that we no longer have a valid
T8b949e// connection

Tff7b72try Tb4b4b4{
Te6edf3sTff7b72?.Te6edf3closeTb4b4b4(Tb4b4b4)
Tb4b4b4} Tff7b72catch Tb4b4b4(Te6edf3_Tb4b4b4: Te6edf3BLEConnectionClosingTb4b4b4) Tb4b4b4{
Te6edf3warnTb4b4b4(Ta5d6ff"Ta5d6ffIgnoring BLE errors while closingTa5d6ff"Tb4b4b4)
Tb4b4b4}
Tb4b4b4} Tff7b72else Tb4b4b4{
Te6edf3debugTb4b4b4(Ta5d6ff"Ta5d6ffRadio was not connected, skipping disableTa5d6ff"Tb4b4b4)
Tb4b4b4}
Tb4b4b4}

T8b949e// / Start a connection attempt
Tff7b72private Tff7b72fun Td2a8ffstartConnectTb4b4b4(Tb4b4b4) Tb4b4b4{
T8b949e// we pass in true for autoconnect - so we will autoconnect whenever the radio
T8b949e// comes in range (even if we made this connect call long ago when we got powered on)
T8b949e// see https://stackoverflow.com/questions/40156699/which-correct-flag-of-autoconnect-in-connectgatt-of-ble for
T8b949e// more info
Te6edf3safeTff7b72!!Tb4b4b4.Te6edf3asyncConnectTb4b4b4(Tff7b72trueTb4b4b4, Te6edf3cb Tff7b72= Tff7b72::Te6edf3onConnectTb4b4b4, Te6edf3lostConnectCb Tff7b72= Tb4b4b4{ Te6edf3scheduleReconnectTb4b4b4(Ta5d6ff"Ta5d6ffconnection droppedTa5d6ff"Tb4b4b4) Tb4b4b4}Tb4b4b4)
Tb4b4b4}

T8b949e/** Get a chracteristic, but in a safe manner because some buggy BLE implementations might return null */
Tff7b72private Tff7b72fun Td2a8ffgetCharacteristicTb4b4b4(Te6edf3uuidTb4b4b4: Te6edf3UUIDTb4b4b4) Tff7b72=
Te6edf3bserviceTb4b4b4.Te6edf3getCharacteristicTb4b4b4(Te6edf3uuidTb4b4b4) Tff7b72?: Tff7b72throw Te6edf3BLECharacteristicNotFoundExceptionTb4b4b4(Te6edf3uuidTb4b4b4)
Tb4b4b4}

Served by rngit 1.5.0 - Generated in 0.15s